﻿using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.IO;
using System.Linq;
using System.Net;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using Nature.Data.CkMarkup;

namespace Nature.Data
{

    public interface IContractsHeap
    {
        System.Collections.IEnumerable ToEntitiesHeap(ModelSetupInfo filter);
    }

    public class ModelFilters
    {
        public Func<IChemicalElement, bool> ChemicalElementFilter { get; set; }

        public Func<ChemicalSpecies, bool> ChemicalSpeciesFilter { get; set; }

        public bool TabulateIdealGasThermodynamicPotentials { get; set; }

        public bool TabulateIdealGasChemicalKineticsProperties { get; set; }

        internal System.Collections.IEnumerable ReduceEntitiesSet(System.Collections.IEnumerable entitiesHeap)
        {
            var includeElement = this.ChemicalElementFilter;
            var includeSpecies = this.ChemicalSpeciesFilter;
            if (includeElement == null &&
                includeSpecies == null) { return entitiesHeap; }
            List<object> entities = new List<object>();
            entities.AddRange(entitiesHeap.Cast<object>());

            var filteredApms = entities.OfType<AtomsPerMoleculeAmount>();
            if (includeElement != null)
            {
                filteredApms = filteredApms.Where(apm => includeElement(apm.Element) == true);
                filteredApms = filteredApms
                    .Where(apm => apm.Species.AtomsPerMoleculeAmounts.All(i => includeElement(i.Element)));
            }
            if (includeSpecies != null)
            {
                filteredApms = filteredApms.Where(apm=> includeSpecies(apm.Species) == true).ToList();
                filteredApms = filteredApms
                    .Where(apm => apm.Element.AtomsPerMoleculeAmounts.Any(i => includeSpecies(i.Species))).ToList();
            }
            filteredApms = filteredApms.ToList();
            var filteredElements = filteredApms.Select(i => i.Element).Distinct().ToList();
            if (includeElement != null)
            {
                filteredElements = filteredElements.Where(i => includeElement(i)).ToList();
            }
            var filteredSpecies = filteredApms.Select(i => i.Species).Distinct().ToList();
            if (includeSpecies != null)
            {
                filteredSpecies = filteredSpecies.Where(i=> includeSpecies(i)).ToList();
            }
            var filteredReaction = entities.OfType<ChemicalReaction>()
                .Where(i => i.Species.All(sp=> filteredSpecies.Contains(sp))).ToList();

            foreach (var el in filteredElements)
            {
                el.AtomsPerMoleculeAmounts = filteredApms.Where(apm=> apm.Element == el).ToArray();
            }
            foreach (var sp in filteredSpecies)
            {
                sp.AtomsPerMoleculeAmounts = filteredApms.Where(apm => apm.Species == sp).ToArray();
            }
            foreach (var reac in filteredReaction.Where(i=> i.Enhanced != null))
            {
                reac.Enhanced = reac.Enhanced.Where(enh => filteredSpecies.Contains(enh.Species)).ToArray();
                reac.Elements = reac.Species.SelectMany(sp => sp.AtomsPerMoleculeAmounts).Select(i => i.Element).Distinct().ToArray();
            }
            foreach (var sp in filteredSpecies)
            {
                sp.Reactions = filteredReaction.Where(r => r.Species.Contains(sp)).ToArray();
            }
            foreach (var el in filteredElements)
            {
                el.Reactions = el.AtomsPerMoleculeAmounts.SelectMany(i => i.Species.Reactions).Distinct().ToArray();
            }

            entities.Clear();
            entities.AddRange(filteredApms);
            entities.AddRange(filteredElements);
            entities.AddRange(filteredSpecies);
            entities.AddRange(filteredReaction);
            entities.AddRange(
                    from sp in filteredSpecies
                    select sp.Thermo7
                );
            entities.AddRange(
                    from sp in filteredSpecies
                    select sp.GasKinParameters
                );
            
            return entities;
        }
    }

    public class ModelSetupInfo : ModelFilters
    {          
        public readonly SpeciesDataFallBackQueue<Thermo7> Thermo7FallBackQueue = new SpeciesDataFallBackQueue<Thermo7>();

        public readonly SpeciesDataFallBackQueue<GasKinParameters> GasKinParametersFallBackQueue = new SpeciesDataFallBackQueue<GasKinParameters>();

    }


    


    public class DataBag : Collection<object>, IContractsHeap
    {        
        public System.Collections.IEnumerable ToEntitiesHeap(ModelSetupInfo filter)
        {
            List<object> entities = new List<object>();
            var readOnlyEntities = new ReadOnlyCollection<object>(entities);

            var speciesListDefinition = this.OfType<ISpeciesListDefinition>().SelectMany(i => i.SpeciesIDs).Distinct().ToList();

            var apm = this.OfType<IAtomsPerMoleculeAmount>().Select(i=> new AtomsPerMoleculeAmount(i));
            if (speciesListDefinition.Any())
            {
                apm = apm.Where(i => speciesListDefinition.Contains(i.SpeciesID));
            }
            else 
            {
                speciesListDefinition = apm.Select(i => i.SpeciesID).Distinct().ToList();
            }
            entities.AddRange( apm.ToList() );

            var elements =
                (from a in entities.OfType<AtomsPerMoleculeAmount>()
                group a by a.ElementID into grp
                select new ChemicalElement 
                {
                    ElementID = grp.Key,
                    ElementName = ChemicalElementMass.GetNameByElementID(grp.Key),
                    ElementMolarMass = ChemicalElementMass.GetMassByElementID(grp.Key),
                    AtomsPerMoleculeAmounts = grp.ToArray()
                }).ToList();
            elements.ForEach(i => {
                foreach (var a in i.AtomsPerMoleculeAmounts) { a.Element = i; }
            });
            entities.AddRange(elements.ToList());

            var species =
                (from a in entities.OfType<AtomsPerMoleculeAmount>()
                group a by a.SpeciesID into grp
                select new ChemicalSpecies 
                {
                    SpeciesID = grp.Key,
                    AtomsPerMoleculeAmounts = grp.ToArray(),
                    SpeciesMolarMass = grp.Sum(i=> i.AtomsPerMolecule * i.Element.ElementMolarMass),
                    Ordinal = speciesListDefinition.IndexOf(grp.Key)
                }).OrderBy(i=> i.Ordinal).ToList();
            entities.AddRange(species);
            species.ForEach(currSpecies =>
            {
                foreach (var a in currSpecies.AtomsPerMoleculeAmounts) { a.Species = currSpecies; }
                var thermo7Items =
                    from th7 in this.OfType<IThermo7>().Where(t => t.SpeciesID == currSpecies.SpeciesID)
                    select new Thermo7
                    {
                        SpeciesID = currSpecies.SpeciesID,
                        Species = currSpecies,
                        Ranges = th7.Ranges.OrderBy(r => r.Tmin).ToArray()
                    };
                var thermo7 = thermo7Items.SingleOrDefault();
                if (thermo7 == null)
                {
                    thermo7 = filter.Thermo7FallBackQueue.Construct(currSpecies.SpeciesID, readOnlyEntities);
                    thermo7.Species = currSpecies;
                }
                var gasKinParams =
                    (
                        from gkp in this.OfType<IGasKinParameters>().Where(i => i.SpeciesID == currSpecies.SpeciesID)
                        select new GasKinParameters(gkp) 
                        {
                            Species = currSpecies
                        }
                    ).SingleOrDefault();
                if (gasKinParams == null)
                {
                    gasKinParams = filter.GasKinParametersFallBackQueue.Construct(currSpecies.SpeciesID, readOnlyEntities);
                    gasKinParams.Species = currSpecies;
                }
                currSpecies.Thermo7 = thermo7;
                currSpecies.GasKinParameters = gasKinParams;
                entities.Add(thermo7);
                entities.Add(gasKinParams);
            });

            var availableReagentIDs = new HashSet<string>(species.Select(i=> i.SpeciesID));
            availableReagentIDs.Add("m");
            var reactions = (from r in this.OfType<IChemicalReaction>() select new ChemicalReaction(r)).ToList();
            reactions = reactions.Where(reac => 
            {
                return reac.Reagents.All(r => availableReagentIDs.Contains(r.SpeciesID)) && 
                    (reac.ThirdBodyID == null || availableReagentIDs.Contains(reac.ThirdBodyID));
            }).ToList();
            if (GetType() == typeof(CkDataBag))
            {
                reactions.ForEach(r => r.Ordinal = reactions.IndexOf(r));
            }
            foreach (var r in reactions.SelectMany(i=> i.Reagents))
            {
                r.Species = species.FirstOrDefault(sp=> sp.SpeciesID == r.SpeciesID);
            }            
            foreach (var enhanced in reactions.Where(i=> i.Enhanced != null).SelectMany(i => i.Enhanced))
            {
                enhanced.Species = species.FirstOrDefault(sp => sp.SpeciesID == enhanced.SpeciesID);
            }
            foreach (var reac in reactions)
            {
                reac.Species = reac.Reagents.Where(i => i.Species != null).Select(i => i.Species).ToArray();
                reac.Elements = reac.Species.SelectMany(sp => sp.AtomsPerMoleculeAmounts).Select(i => i.Element).Distinct().ToArray();
            }
            foreach (var r in reactions.Where(i => i.Enhanced != null))
            {
                r.Enhanced = r.Enhanced.Where(i => i.Species != null);
            }
            foreach (var sp in species)
            {
                sp.Reactions = reactions.Where(r => r.Species.Contains(sp)).ToArray();
            }
            foreach (var el in elements)
            {
                el.Reactions = el.AtomsPerMoleculeAmounts.SelectMany(i => i.Species.Reactions).Distinct().ToArray();
            }

            entities.AddRange(reactions);
            return filter.ReduceEntitiesSet(entities);
        }

        public void AddRange(params object[] contracts)
        {
            foreach (object c in contracts)
            {
                base.Add(c);
            }
        }
    }

    public sealed class CkDataBag : DataBag
    {
        public CkDataBag(params Uri[] ckMarkupResources)
        {
            var tasks =
                (from uri in ckMarkupResources
                 where uri != null
                 select Task.Factory.StartNew<CkTextFragment>(() =>
                 {
                     var webClient = new WebClient();
                     return Regex.IsMatch(uri.AbsolutePath, "(?xis)tran")
                     ? (CkTextFragment)CkTransportSet.Parse((new WebClient()).DownloadString(uri))
                     : (CkTextFragment)CkScript.Parse((new WebClient()).DownloadString(uri));
                 })).ToList();

            foreach (var task in tasks)
            {
                base.Add(task.Result);
                var script = task.Result as CkScript;
                var transport = task.Result as CkTransportSet;
                if (script != null)
                {
                    var speciesIDs =
                        (from spset in script.SpeciesSetsCollection
                         from spid in spset.SpeciesIDs
                         select spid)
                        .Distinct()
                        .ToList();
                    base.Add(new SpeciesListDefinition(speciesIDs));
                    foreach (var apm in
                            from set in script.ThermoSetsCollection
                            from apm in set.AtomsPerMoleculeAmounts
                            select apm)
                    {
                        base.Add(apm);
                    }
                    foreach( var thermo7Contract in
                            from set in script.ThermoSetsCollection
                            from th7 in set.Thermo7Contracts
                            select th7)
                    {
                        base.Add(thermo7Contract);
                    }
                    foreach (var reac in
                        from rset in script.ReactionSetsCollection
                        from reaction in rset.Reactions
                        select reaction)
                    {
                        base.Add(reac);
                    }                       
                }
                else if (transport != null)
                {
                    foreach(var gkp in transport.Items)
                    {
                        base.Add(gkp);
                    }
                }                
            }
        }
    }

}
